#!/usr/bin/env python
# -*- mode: python; coding: utf-8; -*-
##--------------------------------------------------------------------------##
##
## Copyright (C) 1998-2003 Markus Oberhumer <markus@oberhumer.com>
## Copyright (C) 2003 Mt. Hood Playing Card Co.
## Copyright (C) 2005-2010 Skomoroh <skomoroh@users.sourceforge.net>
## Copyright (C) 2010-2013 Jeremy Austin-Bardo <tjaustinbardo@gmail.com>
##
## This program is free software: you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 3 of the License.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program.  If not, see <http://www.gnu.org/licenses/>.
##
##-- G10 -- S03 ------------------------------------------------------------##
__all__ = []


from .. import comparecards as cmp_cards
from .. import formlib as layout
from .. import helpstrings as hs
from .. import pilelib as stack
from ..gamelib import info as gi, state, game
from ..hint import DefaultHint, CautiousDefaultHint, DropbyDefaultHint
from ..packlib import info as cs
from ..utillib.misc import kwdefault

GT, SL = gi.GT, gi.SL
StackWrapper = stack.StackWrapper


class FourByFour_Hint(DropbyDefaultHint):

    def score_move_pile(self, from_stack, to_stack, pile, rpile):
        """
        score_move_pile(from_stack:Stack, to_stack:Stack, pile:seq(Card ...),
          rpile:seq(Card ...)) -> None

        Score moving a pile from stack to to stack.

        Overide **from_stack** builds **to_stack** prefer not play clear stack.
        """
        if not to_stack.cards:
            self._score = 0
        else:
            self._score += 1000 - len(from_stack.cards)


class FourByFour_Foundation(stack.SingleFoundation):
    """
    Build up from any base in rank by same rank wrap. Play none.
    """

    def _sequence(self, cards):
        """
        _sequence(cards:seq) -> bool

        Cards build in rank by same rank wrap.

        :param sequence cards: cards from sending pile
        :return: accept card sequence acccording to *capabilities*?
        :rtype: boolean
        """
        nsuits = self.game['decks'] * len(self.game['suits'])
        self.cap['dir'] = 0
        if (len(self.cards) >= nsuits and
            cmp_cards.isRankSequence(self.cards[-nsuits:], **self.cap)):
            self.cap['dir'] = 1
        return cmp_cards.isRankSequence(cards, **self.cap)

    @property
    def _helpString(self):
        """
        _helpString -> str

        :return: helpful string about pile for its canvas label.
        :rtype:  base_string
        """
        return hs.BUILD_SRK_DICT.get(cmp(self.cap['dir'], 0), '')


class FourbyFour_RowStack(stack.UD_RK_RowStack):
    """
    Build either in rank alone. Play top. _BOTTOM_IMAGE_ is shade.
    """
    _BOTTOM_IMAGE_ = 'shade'


class PileOn_RowStack(stack.RK_RowStack):
    """
    Pair by same rank. Play seq. _BOTTOM_IMAGE_ is shade. Flip over any full
    seq.
    """
    _BOTTOM_IMAGE_ = 'shade'

    def closeStack(self):
        """
        closeStack() -> None

        Overide to perfom after addCard or insertCard a flipAllMove and
        is_filled on pile with max_cards.
        """
        if (len(self.cards) == self.cap['max_cards'] and
            self._sequence(self.cards)):
            self._is_filled = True
            if not self.game.move_state == self.game.S_REDO:
                self.game.move_pile_flip(self)

    def cardFlips(self):
        """
        cardFlips() -> bool

        :return: flip |top|?
        :rtype: boolean
        """
        return False


class FourSome_Reserve(stack.ReserveStack):
    """
    Build 1 card. Drop each pile when same rank as 1st. Play top.
    """

    @state.move_state('fill')
    def _auto_fill(self, stack):
        """
        Perfom actions after a move_pile() drop all _reserve_ when each filled
        with same rank on _foundation_.
        """
        reserves, foundation = self.game.s['reserves'], self.s['foundations'][0]
        if not reserves[0].cards:
            return
        rank = reserves[0].BASECARD['rank']
        cards = [s.cards for s in reserves]
        if (len(cards) == len(reserves) and cmp_cards.isSameRank(cards, rank)):
            if self.game.can_play_sound:
                self.game._play_sound('droppair', priority=200)
            for pile in reserves:
                self.game.fill(self, to_stack=foundation, frames=4)


class PileOn(game.Game):
    """
    Arrange cards on _row_ within a turn.

    _row_
        Pile 15 over 4 row. Init deal 13 pile 4 open card. Pair by same rank.
        Hold max. 4 card. Play seq.
    ------
    :cvar Stack _talon_: stack.InitialDealTalon
    :cvar Stack _row_: PileOn_RowStack (dir=0, max_cards=4)
    """
    __info__ = dict(id=41, name='PileOn', game_type=(GT.ONE_DECK | GT.OPEN |
                    GT.GNOME), decks=1, skill_level=SL.MOSTLY_SKILL,
                    altnames=('Fifteen Puzzle',), version='3.00')
    __author__ = 'Markus Oberhumer'
    __version__ = 7.70

    _talon_ = stack.InitialDealTalon
    _row_ = StackWrapper(PileOn_RowStack, dir=0, max_cards=4)

    def _create_game(self, rows=(4,) * 3 + (3,), playcards=7, **kw):
        l, s = layout.Layout(self), self.s
        dx = l._get_stack_width(playcards)
        self.table_size = (l.XM + max(rows) * dx, l.YM + (len(rows) + 1) * l.YS,)
        y = l.YM
        for y, row in zip(l.col_iter(l.YM, len(rows)), rows):
            for x in l.row_iter(l.XM, row, width=dx):
                s['rows'].append(self._row_(x, y, game=self,
                                         offset=(l.XOFFSET, 0)))
        x, y = self.width - l.XS, self.height - l.YS
        s['talon'] = self._talon_(x, y, game=self)
        self.sg['openstacks'] = s['rows']
        self.sg['Talons'] = [s['talon']]
        self.sg['dropstacks'] = s['rows']
        return l

    def _start(self, **kw):
        for i in range(self.s['rows'][0].cap['maxcards'] - 1):
            self.s['talon'].dealRow(rows=self.s['rows'][:-2], frames=0)
        self._play_sound('deal')
        self.s['talon'].dealRow(rows=self.s['rows'][:-2])

    @property
    def is_complete(self):
        return all(r._is_filled for r in self.s['rows'])


class SmallPileOn(PileOn):
    """
    Arrange cards on _row_ within a turn.

    _row_
        Pile 11 over 4 row. Init deal 9 pile 4 open card. Pair by same rank.
        Hold max. 4 card. Play seq.
    ------
    :cvar Stack _talon_: stack.InitialDealTalon
    :cvar Stack _row_: PileOn_RowStack (dir=0, max_cards=4)
    """
    __info__ = dict(id=289, name='Small PileOn', game_type=(GT.ONE_DECK |
                    GT.OPEN | GT.ORIGINAL), decks=1,
                    skill_level=SL.MOSTLY_SKILL, ranks=cs.SHORT_A6K,
                    version='0.80fc')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    def _create_game(self, **kw):
        kwdefault(kw, rows=(3,) * 3 + (2,), playcards=7)
        return super(SmallPileOn, self)._create_game(**kw)


class PileOn2Decks(PileOn):
    """
    Arrange cards on _row_ within a turn.

    _row_
        Pile 15 over 4 row. Init deal 13 pile 8 open card. Pair by same rank.
        Hold max. 8  card. Play seq.

    ------
    :cvar Stack _talon_: stack.InitialDealTalon
    :cvar Stack _row_: PileOn_RowStack (dir=0, max_cards=8)
    """
    __info__ = dict(id=341, name='PileOn (2 decks)', game_type=(GT.TWO_DECK |
                    GT.OPEN | GT.ORIGINAL), decks=2,
                    skill_level=SL.MOSTLY_SKILL, version='0.70up')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _row_ = StackWrapper(PileOn_RowStack, dir=0, max_cards=8)

    def _create_game(self, **kw):
        kw.setdefault('playcards', 11)
        return super(PileOn2Decks, self)._create_game(**kw)


class Foursome(game.Game):
    """
    Finish cards on _foundation_ within a turn.

    _row_
      Pile 6 horz. Init deal 1 card. Build either in rank by alt color. Drop
      full seq on _foundation_.

    _reserve_
      Pile 4 horz N _row_. Init deal 1 card. Build 1 card. Play top. Drop each
      pile when same rank as 1st.

    _talon_
      Pile 1 NW. Deal _row_ 1 card.

    _foundation_
      Pile 1 NE. Drop seq from _row_.
    ------
    :cvar Stack _talon_: stack.DealRowTalon
    :cvar Stack _foundation_: stack.SingleFoundation
    :cvar Stack _row_: stack.UD_AC_RowStack (mod=13)
    :cvar Stack _reserve_: FourSome_Reserve
    :cvar Hint _hint_: hint.CautiousDefaultHint
    """
    __info__ = dict(id=554, name='Foursome', game_type=GT.ONE_DECK, decks=1,
                    skill_level=SL.MOSTLY_SKILL, version='0.93fc')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _talon_ = stack.DealRowTalon
    _foundation_ = stack.SingleFoundation
    _row_ = StackWrapper(stack.UD_AC_RowStack, mod=13)
    _reserve_ = FourSome_Reserve
    _hint_ = CautiousDefaultHint

    def _create_game(self, rows=6, reserves=4, playcards=13, **kw):
        l, s = layout.Layout(self), self.s
        max_rows = max(reserves + 2, rows)
        self.table_size = (l.XM + max_rows * l.XS,
                           l.YM + l._get_stack_height(playcards, l.YS),)
        center = lambda p: l.XM + (max_rows - p) * l.XS / 2
        x, y = l.XM, l.YM
        s['talon'] = self._talon_(x, y, game=self)
        l.set_ncards_text(s['talon'], 'ne', always=False)
        for x in l.row_iter(center(reserves), reserves):
            s['reserves'].append(self._reserve_(x, y, game=self))
        x = self.width - l.XS
        s['foundations'].append(self._foundation_(x, y, game=self))
        l.set_ncards_text(s['foundations'][-1], 'nw')
        y += l.YS
        for x in l.row_iter(center(rows), rows):
            s['rows'].append(self._row_(x, y, game=self))
        l.defaultStackGroups()
        return l

    def _start(self, **kw):
        self._play_sound('deal')
        self.s['talon'].dealRow(rows=self.s['reserves'])
        self.s['talon'].dealRow()


class Quartets(Foursome):
    """
    Finish cards on _foundation_ within a turn.

    _row_
      Pile 8 horz. Init deal [7, 7, 7, 7, 6, 6, 6, 6] card. Build either in
      rank by alt color. Drop full seq on _foundation_.

    _reserve_
      Pile 4 horz N _row_. Init deal 1 card. Build 1 card. Play top.

    _foundation_
      Pile 1 NE. Drop seq from _row_.
    ------
    :cvar Stack _talon_: stack.InitialDealTalon
    :cvar Stack _foundation_: stack.SingleFoundation
    :cvar Stack _row_: stack.UD_AC_RowStack (mod=13)
    :cvar Stack _reserve_: FourSome_Reserve
    :cvar Hint _hint_: hint.CautiousDefaultHint
    """
    __info__ = dict(id=555, name='Quartets', game_type=(GT.ONE_DECK | GT.OPEN
                    | GT.ORIGINAL), decks=1,
                    skill_level=SL.MOSTLY_SKILL, version='0.93fc')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _talon_ = stack.InitialDealTalon

    def _create_game(self, **kw):
        kw.setdefault('rows', 8)
        return super(Quartets, self)._create_game(**kw)

    def _start(self, rows=7, **kw):
        for i in range(rows - 2):
            self.s['talon'].dealRow(frames=0)
        self._play_sound('deal')
        self.s['talon'].dealRow()
        self.s['talon'].dealRowAvail()


class FourByFour(game.Game):
    """
    Finish cards on _foundation_ within a turn.

    _row_
      Pile 4 horz. Init deal 1 card. Build either in rank alone. Play top.

    _talon_
      Pile 1 NW. Deal _waste_ 1 card.

    _waste_
      Pile 1 E _talon_. Init deal 1 card. Build none. Play top

    _foundation_
      Pile 1 NE. Build up from any base in rank by same rank wrap. Play none.
    ------
    :cvar Stack _talon_: stack.WasteTalon
    :cvar Stack _waste_: stack.WasteStack
    :cvar Stack _foundation_: FourByFour_Foundation (max_accept=1, mod=13)
    :cvar Stack _row_: FourbyFour (mod=13)
    :cvar Hint _hint_: FourByFour_Hint
    """
    __info__ = dict(id=703, name='Four by Four', game_type=GT.ONE_DECK,
                    decks=1, skill_level=SL.BALANCED,
                    version='1.00fc')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _talon_ = stack.WasteTalon
    _waste_ = stack.WasteStack
    _foundation_ = StackWrapper(FourByFour_Foundation, max_accept=1, mod=13)
    _row_ = StackWrapper(FourbyFour_RowStack, mod=13)
    _hint_ = FourByFour_Hint

    def _create_game(self, rows=4, playcards=20, **kw):
        l, s = layout.Layout(self), self.s
        self.table_size = (l.XM + 7 * l.XS,
                           l.YM + l._get_stack_height(playcards, l.YS))
        right = lambda p: self.width - p * l.XS
        x, y = l.XM, l.YM
        s['talon'] = self._talon_(x, y, game=self)
        l.set_ncards_text(s['talon'], 's')
        x += l.XS
        s['wastes'].append(self._waste_(x, y, game=self))
        l.set_ncards_text(s['wastes'][-1], 's')
        x = right(1)
        s['foundations'].append(self._foundation_(x, y, game=self))
        l.set_misc_text(s['foundations'][-1], 'ne')
        y += l.YS
        for x in l.row_iter(right(rows), rows):
            s['rows'].append(self._row_(x, y, game=self))
        l.defaultStackGroups()
        return l

    def _start(self):
        self._play_sound('deal')
        self.s['talon'].dealRow(rows=self.s['foundations'])
        self.s['talon'].deal_cards()

    @state.not_state('is_preview')
    def _update_text(self):
        # TODO: Move to pile method
        foundation = self.s['foundations'][0]
        nsuits = self['decks'] * len(self['suits'])
        if not foundation.cards:
            base_rank = foundation.cap['base_rank']
            if base_rank == cs.ANY:
                t = ''
            else:
                t = '{0} ({1})'.format(self._RANKS_[base_rank], nsuits)
        elif len(foundation.cards) == self['ncards']:
            t = ''
        else:
            rank = foundation.TOPCARD['rank']
            cards = foundation[-nsuits:]
            same_rank = sum(1 for c in cards if c['rank'] == rank) % nsuits
            if same_rank == 0:
                rank = (rank + 1) % foundation.cap['mod']
            t = '{0} ({1})'.format(self._RANKS_[rank], same_rank)
        foundation['texts']['misc'].config(text=t)


class Footling(FourByFour):
    """
    Finish cards on _foundation_ within a turn.

    _row_
      Pile 8 horz. Init deal [7, 7, 7, 7, 6, 6, 6, 6] open card. Build down in
      rank by alt color. Play seq.

    _reserve_
      Pile 4 horz NW. Build 1 card. Play top

    _foundation_
      Pile 1 NE. Build up in rank by same rank. Play none.
    ------
    :cvar Stack _talon_: stack.InitialDealTalon
    :cvar Stack _waste_: None
    :cvar Stack _foundation_: FourByFour_Foundation (max_accept=1,
      base_rank=ACE)
    :cvar Stack _row_: stack.AC_RowStack
    :cvar Stack _reserve_: stack.ReserveStack
    :cvar Hint _hint_: hint.DefaultHint
    """
    __info__ = dict(id=740, name='Footling', game_type=(GT.FREECELL | GT.OPEN
                    | GT.ORIGINAL), decks=1, version='1.10fc',
                    skill_level=SL.MOSTLY_SKILL)
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _talon_ = stack.InitialDealTalon
    _waste_ = None
    _foundation_ = StackWrapper(FourByFour_Foundation, max_accept=1,
                                base_rank=cs.ACE)
    _row_ = stack.AC_RowStack
    _reserve_ = stack.ReserveStack
    _hint_ = DefaultHint

    def _create_game(self, rows=8, reserves=4, playcards=15):
        l, s = layout.Layout(self), self.s
        self.table_size = (l.XM + rows * l.XS,
                           l.YM + l.YS + l._get_stack_height(playcards))
        y = l.YM
        for x in l.row_iter(l.XM, reserves):
            s['reserves'].append(self._reserve_(x, y, game=self))
        x = self.width - 2 * l.XS
        s['foundations'].append(self._foundation_(x, y, game=self))
        l.set_misc_text(s['foundations'][-1], 'ne')
        y += l.YS
        for x in l.row_iter(l.XM, rows):
            s['rows'].append(self._row_(x, y, game=self))
        x, y = l.XM, self.height - l.YS
        s['talon'] = self._talon_(x, y, game=self)
        l.defaultStackGroups()
        return l

    def _start(self, rows=7, **kw):
        for i in range(rows - 2):
            self.s['talon'].dealRow(frames=0)
        self._play_sound('deal')
        self.s['talon'].dealRow()
        self.s['talon'].dealRowAvail()


class DoubleFootling(Footling):
    """
    Finish cards on _foundation_ within a turn.

    _row_
      Pile 10 horz. Init deal [11, 11, 11, 11, 10, 10, 10, 10, 10, 10] open
      card. Build down in rank by alt color. Play seq.

    _reserve_
      Pile 5 horz NW. Build 1 card. Play top

    _foundation_
      Pile 1 NE. Build up in rank by same rank. Play none.
    ------
    :cvar Stack _talon_: stack.InitialDealTalon
    :cvar Stack _waste_: None
    :cvar Stack _foundation_: FourByFour_Foundation (max_accept=1,
      base_rank=ACE)
    :cvar Stack _row_: stack.AC_RowStack
    :cvar Stack _reserve_: stack.ReserveStack
    :cvar Hint _hint_: hint.DefaultHint
    """
    __info__ = dict(id=741, name='Double Footling', game_type=(GT.FREECELL
                    | GT.OPEN | GT.ORIGINAL), decks=2,
                    skill_level=SL.MOSTLY_SKILL, version='1.10fc',
                    altnames=('Footling (2 Decks)',))
    __author__ = 'Skomoroh'
    __version__ = 7.70

    def _create_game(self, **kw):
        kwdefault(kw, rows=10, reserves=5, playcards=18)
        return super(DoubleFootling, self)._create_game(**kw)

    def _start(self, **kw):
        kw.setdefault('rows', 11)
        super(DoubleFootling, self)._start(**kw)


class Flake(game.Game):
    """
    Finish cards on _foundation_ within a turn.

    _row_
      Pile 6 horz S _foundation_. Init deal [9, 9, 9, 9, 8, 8] open card.
      Build either in rank alone wrap. Play top.

    _foundation_
      Pile 1 N. Build either in rank alone wrap. Play none.
    ------
    :cvar Stack _talon_: stack.InitialDealTalon
    :cvar Stack _foundation_: stack.UD_RK_Foundation (max_move=0, suit=ANY,
      base_rank=ANY, dir=0, mod=13, max_cards=52)
    :cvar Stack _row_: stack.UD_RK_RowStack (mod=13)
    :cvar Hint _hint_: FourByFour_Hint
    """
    __info__ = dict(id=749, name='Flake', game_type=(GT.GOLF | GT.OPEN |
                    GT.ORIGINAL), decks=1, version='1.10fc',
                    skill_level=SL.MOSTLY_SKILL)
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _talon_ = stack.InitialDealTalon
    _foundation_ = StackWrapper(stack.UD_RK_Foundation, max_move=0, suit=cs.ANY,
                                base_rank=cs.ANY, dir=0, mod=13, max_cards=52)
    _row_ = StackWrapper(stack.UD_RK_RowStack, mod=13)
    _hint_ = FourByFour_Hint

    def _create_game(self, rows=6, playcards=18, **kw):
        l, s = layout.Layout(self), self.s
        self.table_size = (l.XM + rows * l.XS,
                           l.YM + l.YS + l._get_stack_height(playcards))
        x, y, = l.XM, l.YM + l.YS
        for i in l.row_iter(l.XM, rows):
            s['rows'].append(self._row_(x, y, game=self))
        x, y = l.XM + (rows - 1) * l.XS / 2, l.YM
        s['foundations'].append(stack.UD_RK_Foundation(x, y, game=self))
        l.set_ncards_text(s['foundations'][-1], 'ne')
        x, y = l.XM, self.height - l.YS
        s['talon'] = (x, y, self)
        l.defaultStackGroups()
        return l

    def _start(self, rows=7, **kw):
        for i in range(rows - 2):
            self.s['talon'].dealRow(frames=0)
        self._play_sound('deal')
        self.s['talon'].dealRow()
        self.s['talon'].dealRowAvail()


class Flake2Decks(Flake):
    """
    Finish cards on _foundation_ within a turn.

    _row_
      Pile 8 horz S _foundation_. Init deal 13 open card. Build either in rank
      alone wrap. Play top.

    _foundation_
      Pile 1 N. Build either in rank alone wrap. Play none.
    ------
    :cvar Stack _talon_: stack.InitialDealTalon
    :cvar Stack _foundation_: stack.UD_RK_Foundation (max_move=0, suit=ANY,
      base_rank=ANY, dir=0, mod=13, max_cards=52)
    :cvar Stack _row_: stack.UD_RK_RowStack (mod=13)
    :cvar Hint _hint_: FourByFour_Hint
    """
    __info__ = dict(id=750, name='Flake (2 Decks)', game_type=(GT.GOLF |
                    GT.OPEN | GT.ORIGINAL), decks=2, version='1.10fc',
                    skill_level=SL.MOSTLY_SKILL)
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _foundation_ = StackWrapper(stack.UD_RK_Foundation, max_move=0, suit=cs.ANY,
                                base_rank=cs.ANY, dir=0, mod=13, max_cards=104)

    def _create_game(self, **kw):
        kwdefault(kw, rows=8, playcards=22)
        super(Flake2Decks, self)._create_game(**kw)

    def _start(self, **kw):
        kw.setdefault('rows', 13)
        super(Flake2Decks, self)._start(**kw)


PROVIDES = [PileOn, SmallPileOn, Foursome, Quartets, FourByFour,
            Footling, DoubleFootling, Flake, Flake2Decks]